package org.xian.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class NIOServer implements Runnable {

    /**
     * Java NIO 编程的核心：多路复用器
     */
    private Selector selector;

    /**
     * 服务器的 channel
     */
    private ServerSocketChannel serverSocketChannel;

    private final int port = 8080;

    private boolean isStart = false;

    public NIOServer() {
        try {
            // 1、创建多路复用器
            this.selector = Selector.open();
            // 2、开启服务端 channel ，设置为非阻塞的，监听端口 8080
            this.serverSocketChannel = ServerSocketChannel.open();
            this.serverSocketChannel.configureBlocking(false);
            this.serverSocketChannel.bind(new InetSocketAddress(port));
            // 3、将服务端的 channel 注册到 selector ，监听 OP_ACCEPT 事件
            this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NIO Server Started -->" + this.serverSocketChannel.getLocalAddress());
            this.isStart = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        // 这里这里处理多路复用器 selector 的事件
        // SelectionKey.OP_ACCEPT : 表示 ServerSocketChannel 接受客户端连接的事件
        // SelectionKey.OP_CONNECT: 表示客户端的 SocketChannel 去连接服务端
        // SelectionKey.OP_READ   : 表示读数据准备就绪的
        // SelectionKey.OP_WRITE  : 表示写数据准备就绪
        while (this.isStart) {
            // 不断轮训注册到 selector 的事件是否就绪
            try {
                // 1、这里阻塞到至少有一个事件就绪
                this.selector.select();
                // selector.select(long timeOut) 表示最多阻塞多长时间，没有就绪的事件就 timeOut
                // 2、多路复用器已经就绪的结果集
                Iterator<SelectionKey> selectionKeys = this.selector.selectedKeys().iterator();
                while (selectionKeys.hasNext()) {
                    // 3、取出一个准备就绪的 key
                    SelectionKey selectionKey = selectionKeys.next();
                    selectionKeys.remove();
                    // 4、处理这些 key
                    // 新建立的连接
                    if (selectionKey.isAcceptable()) {
                        accept(selectionKey);
                    }
                    // 读取客户端输入
                    if (selectionKey.isReadable()) {
                        read(selectionKey);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void accept(SelectionKey selectionKey) {
        // 处理新建立的连接
        // 1、获得服务端 channel
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        try {
            // 2、阻塞等待和客户建立连接
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 3、设置为非阻塞模式
            socketChannel.configureBlocking(false);
            // 4、将和客户端端连接的 channel 注册到 selector,并设置 READ 事件就绪
            socketChannel.register(this.selector, SelectionKey.OP_READ);
            System.out.println("New Client From ->" + socketChannel.getRemoteAddress());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void read(SelectionKey selectionKey) {
        // 处理客户端发送来的消息
        // 1、获得和客户端连接的 SocketChannel
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 2、创建 ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            // 3、读取数据，并返回已经读取的字节数
            int readBytes = socketChannel.read(buffer);
            if (readBytes > 0) {
                // buffer 切换到读模式
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                String message = new String(bytes, StandardCharsets.UTF_8);
                System.out.println("Server Received --> " + message + ", From --> " + socketChannel.getRemoteAddress());
                // 4、收到的信息直接 pong 回去, 不重新申请 buffer， 直接用原来的 buffer 复位读一下
                buffer.rewind();
                socketChannel.write(buffer);
                // 5、关闭连接
                if ("bye".equals(message)) {
                    System.out.println("Client Closed -->" + socketChannel.getRemoteAddress());
                    selectionKey.cancel();
                    socketChannel.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(new NIOServer()).start();
    }
}
